/*
 * RestoreDestination.java
 *
 * Created on August 4, 2007, 3:48 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.atomojo.auth.service.db;

import java.security.NoSuchAlgorithmException;
import java.sql.SQLException;
import java.util.UUID;
import java.util.logging.Logger;
import org.infoset.xml.Element;
import org.infoset.xml.ElementEnd;
import org.infoset.xml.Item;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.Name;
import org.infoset.xml.Text;
import org.infoset.xml.XMLException;
import org.milowski.db.DB;

/**
 *
 * @author alex
 */
public class RestoreDestination implements ItemDestination
{
   
   interface HandlerDestination extends ItemDestination {
      int getStartLevel();
      HandlerDestination pop();
   }
   class PermissionsDestination implements HandlerDestination {
      HandlerDestination parent;
      PermissionsDestination(HandlerDestination parent) {
         this.parent = parent;
      }
      public int getStartLevel() {
         return 2;
      }
      public HandlerDestination pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         /*
         if (level==2 && (item.getType()==Item.ItemType.ElementItem || item.getType()==Item.ItemType.ElementEndItem)) {
            log.info(item.getType()==Item.ItemType.ElementItem ? "Starting locks:" : "Ending locks:");
            try {
               db.query("SELECT xid,type,mode,tablename FROM NEW org.apache.derby.diag.LockTable() AS LT",new DB.DBResultHandler() {
                  public void onResults(ResultSet set) 
                     throws SQLException
                  {
                     while (set.next()) {
                        log.info("LOCK: "+set.getString(1)+","+set.getString(2)+","+set.getString(3)+","+set.getString(4));
                     }
                  }
               });
            } catch (SQLException ex) {
               ex.printStackTrace();
            }
            
         }
          */
         if (level==3) {
            if (item.getType()==Item.ItemType.ElementItem) {
               Element e = (Element)item;
               if (e.getName().equals(XML.PERMISSION_NAME)) {
                  String sid = e.getAttributeValue("id");
                  String name = e.getAttributeValue("name");
                  if (sid==null) {
                     log.warning("A permission is missing the 'id' attribute.");
                     return;
                  }
                  if (name==null) {
                     log.warning("A permission is missing the 'name' attribute.");
                     return;
                  }
                  try {
                     UUID uuid = UUID.fromString(sid);
                     Permission p = db.getPermission(uuid);
                     if (p==null) {
                        log.info("Creating permission ("+name+","+sid+")");
                        p = db.createPermission(name,uuid);
                     } else {
                        if (!p.getName().equals(name)) {
                           log.warning("Permission ("+name+","+sid+") exists but has a different name, recreating.");
                           p.delete();
                           p = db.createPermission(name,uuid);
                        } else {
                           log.info("Permission ("+name+","+sid+") already exists.");
                        }
                     }
                  } catch (IllegalArgumentException ex) {
                     log.warning("Bad UUID value '"+sid+"' on permission.");
                  } catch (SQLException ex) {
                     throw new XMLException("Database error creating permission "+sid,ex);
                  }
               }
            }
         }
      }
   }
   class RolesDestination implements HandlerDestination {
      Role role;
      HandlerDestination parent;
      RolesDestination(HandlerDestination parent) {
         this.parent = parent;
      }
      public int getStartLevel() {
         return 2;
      }
      public HandlerDestination pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (level==3) {
            switch (item.getType()) {
               case ElementItem:
                  Element e = (Element)item;
                  if (e.getName().equals(XML.ROLE_NAME)) {
                     String sid = e.getAttributeValue("id");
                     String name = e.getAttributeValue("name");
                     if (sid==null) {
                        log.warning("A role is missing the 'id' attribute.");
                        return;
                     }
                     if (name==null) {
                        log.warning("A role is missing the 'name' attribute.");
                        return;
                     }
                     try {
                        UUID uuid = UUID.fromString(sid);
                        role = db.getRole(uuid);
                        if (role==null) {
                           log.info("Creating role ("+name+","+sid+")");
                           role = db.createRole(name,uuid);
                        } else {
                           if (!role.getName().equals(name)) {
                              log.warning("Role ("+name+","+sid+") exists but has a different name, recreating.");
                              role.delete();
                              role = db.createRole(name,uuid);
                           } else {
                              log.info("Role ("+name+","+sid+") already exists.");
                           }
                        }
                     } catch (IllegalArgumentException ex) {
                        log.warning("Bad UUID value '"+sid+"' on role.");
                     } catch (SQLException ex) {
                        throw new XMLException("Database error creating role "+sid,ex);
                     }
                  }
               break;
               case ElementEndItem:
                  role = null;
            }
         } else if (level==4 && role!=null) {
            if (item.getType()==Item.ItemType.ElementItem) {
               Element e = (Element)item;
               if (e.getName().equals(XML.PERMISSION_NAME)) {
                  String sid = e.getAttributeValue("id");
                  String name = e.getAttributeValue("name");
                  if (sid==null && name==null) {
                     log.warning("A permission for role "+role.getUUID()+" must have at least the 'id' or 'name' attribute.");
                     return;
                  }
                  try {
                     Permission p = null;
                     if (sid!=null) {
                        UUID uuid = UUID.fromString(sid);
                        p = db.getPermission(uuid);
                     }
                     if (p==null && name!=null) {
                        p = db.getPermission(name);
                     }
                     if (p==null) {
                        log.warning("Cannot find permission ("+sid+","+name+") for role "+role.getUUID());
                     }
                     if (!role.hasPermission(p)) {
                        log.info("Adding permission ("+p.getName()+","+p.getUUID()+") to role.");
                        if (!role.addPermission(p)) {
                           log.warning("Cannot add permission "+p.getUUID()+" to role "+role.getUUID());
                        }
                     } else {
                        log.info("Role already has permission ("+p.getName()+","+p.getUUID()+") to role.");
                     }
                  } catch (IllegalArgumentException ex) {
                     log.warning("Bad UUID value '"+sid+"' on permission.");
                  } catch (SQLException ex) {
                     throw new XMLException("Database error adding permission "+sid+" to role "+role.getUUID(),ex);
                  }
               }
            }
         }
         
      }
   }
   class UsersDestination implements HandlerDestination {
      HandlerDestination parent;
      String alias;
      UUID id;
      String name;
      String email;
      String passwordMd5;
      StringBuilder buffer;
      UsersDestination(HandlerDestination parent) {
         this.parent = parent;
      }
      public int getStartLevel() {
         return 2;
      }
      public HandlerDestination pop() {
         return parent;
      }
      User create() 
         throws XMLException
      {
         User user = null;
         if (id!=null) {
            try {
               user = db.getUser(id);
               if (user!=null) {
                  log.info("Changing user ("+alias+","+id+","+name+","+email+")");
                  if (!user.changeAlias(alias)) {
                     log.warning("Could not change alias to '"+alias+"'");
                  }
                  user.setName(name);
                  /*
                  db.query("SELECT xid,type,mode,tablename FROM NEW org.apache.derby.diag.LockTable() AS LT",new DB.DBResultHandler() {
                     public void onResults(ResultSet set) 
                        throws SQLException
                     {
                        while (set.next()) {
                           log.info("LOCK: "+set.getString(1)+","+set.getString(2)+","+set.getString(3)+","+set.getString(4));
                        }
                     }
                  });
                   */
                  user.setEmail(email);
                  if (passwordMd5!=null) {
                     log.info("Setting password for "+id);
                     user.setEncryptedPassword("md5",passwordMd5);
                  }
               } else {
                  log.info("Creating user ("+alias+","+id+","+name+","+email+")");
                  user = db.createUser(id,alias,name,email);
                  if (user==null) {
                     if (alias.equals("admin")) {
                        user = db.getUser("admin");
                        if (user!=null && user.hasPermission(db.getPermission(AuthDB.SUPERUSER_PERMISSION))) {
                           // we have a admin user to restore
                           user.delete();
                           user = db.createUser(id,alias,name,email);
                           if (user==null) {
                              log.severe("Cannot recreate admin user!");
                           }
                        } else {
                           log.warning("Creation of user with alias "+alias+" refused.");
                        }
                     } else {
                        log.warning("Creation of user with alias "+alias+" refused.");
                     }
                  } 
                  if (user!=null && passwordMd5!=null) {
                     user.setEncryptedPassword("md5",passwordMd5);
                  }
               }
               id = null;
               name = null;
               email = null;
               alias = null;
               passwordMd5 = null;
            } catch (SQLException ex) {
               throw new XMLException("Database error while creating user "+id,ex);
            } catch (NoSuchAlgorithmException ex) {
               throw new XMLException("Password for user "+id+" could not be set.",ex);
            }
         }
         return user;
         
      }
      public void send(Item item)
         throws XMLException
      {
         if (level==3) {
            switch (item.getType()) {
               case ElementItem:
                  Element e = (Element)item;
                  if (e.getName().equals(XML.USER_NAME)) {
                     String sid = e.getAttributeValue("id");
                     alias = e.getAttributeValue("alias");
                     passwordMd5 = e.getAttributeValue("password-md5");
                     if (sid==null) {
                        log.warning("A user is missing the 'id' attribute.");
                        return;
                     }
                     try {
                        id = UUID.fromString(sid);
                     } catch (IllegalArgumentException ex) {
                        log.warning("Bad UUID value '"+sid+"' on user.");
                     }
                     name = null;
                     email = null;
                  }
               break;
               case ElementEndItem:
                  create();
            }
         } else if (level==4) {
            switch (item.getType()) {
               case ElementItem:
               {
                  Element e = (Element)item;
                  if (e.getName().equals(XML.NAME_NAME)) {
                     buffer = new StringBuilder();
                  } else if (e.getName().equals(XML.EMAIL_NAME)) {
                     buffer = new StringBuilder();
                  } else if (e.getName().equals(XML.ROLES_NAME)) {
                     User user = create();
                     if (user!=null) {
                        current = new UserRolesDestination(user,current);
                        current.send(item);
                     }
                     id = null;
                  }
               }
                  break;
               case ElementEndItem:
               {
                  ElementEnd e = (ElementEnd)item;
                  if (e.getName().equals(XML.NAME_NAME)) {
                     name = buffer.toString();
                  } else if (e.getName().equals(XML.EMAIL_NAME)) {
                     email = buffer.toString();
                  }
                  buffer = null;
                  break;
               }
               case CharactersItem:
                  if (buffer!=null) {
                     buffer.append(((Text)item).getText());
                  }
                  
            }
         } else if (item.getType()==Item.ItemType.CharactersItem &&  buffer!=null) {
            buffer.append(((Text)item).getText());
         }
         
      }
   }
   class UserRolesDestination implements HandlerDestination {
      HandlerDestination parent;
      User user;
      UserRolesDestination(User user,HandlerDestination parent) {
         this.parent = parent;
         this.user = user;
      }
      public int getStartLevel() {
         return 4;
      }
      public HandlerDestination pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (level==5 && item.getType()==Item.ItemType.ElementItem) {
            Element e = (Element)item;
            if (e.getName().equals(XML.ROLE_NAME)) {
               String sid = e.getAttributeValue("id");
               if (sid==null) {
                  log.warning("A role in user ("+user.getAlias()+","+user.getUUID()+") is missing the 'id' attribute.");
                  return;
               }
               try {
                  UUID id = UUID.fromString(sid);
                  Role role = db.getRole(id);
                  if (role==null) {
                     log.warning("Cannot find role "+id+" for user ("+user.getAlias()+","+user.getUUID()+").");
                  } else {
                     log.info("Adding role ("+role.getName()+","+role.getUUID()+")");
                     user.addRole(role);
                  }
               } catch (SQLException ex) {
                  throw new XMLException("Database error while adding role "+sid+" to group ("+user.getAlias()+","+user.getUUID()+").",ex);
               } catch (IllegalArgumentException ex) {
                  log.warning("Bad UUID value '"+sid+"' on role in group ("+user.getAlias()+","+user.getUUID()+").");
               }
            }
         }
      }
   }
   class RealmsDestination implements HandlerDestination {
      Realm realm;
      HandlerDestination parent;
      RealmsDestination(HandlerDestination parent) {
         this.parent = parent;
      }
      public int getStartLevel() {
         return 2;
      }
      public HandlerDestination pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (level==3) {
            switch (item.getType()) {
               case ElementItem:
                  Element e = (Element)item;
                  if (e.getName().equals(XML.REALM_NAME)) {
                     String sid = e.getAttributeValue("id");
                     String name = e.getAttributeValue("name");
                     if (sid==null) {
                        log.warning("A realm is missing the 'id' attribute.");
                        return;
                     }
                     if (name==null) {
                        log.warning("A realm is missing the 'realm' attribute.");
                        return;
                     }
                     try {
                        UUID id = UUID.fromString(sid);
                        realm = db.getRealm(id);
                        if (realm!=null && !realm.getName().equals(name)) {
                           log.warning("Realm ("+name+","+sid+" exists but has a different name, recreating.");
                           realm.delete();
                           realm = null;
                        } else if (realm!=null) {
                           log.info("Realm ("+name+","+sid+") already exists.");
                        }
                        if (realm==null) {
                           log.info("Creating realm ("+name+","+sid+").");
                           realm = db.createRealm(name,id);
                        }
                     } catch (IllegalArgumentException ex) {
                        log.warning("Bad UUID value '"+sid+"' on realm.");
                     } catch (SQLException ex) {
                        throw new XMLException("Database error on realm "+sid,ex);
                     }
                  }
               break;
               case ElementEndItem:
                  realm = null;
            }
         } else if (level==4) {
            switch (item.getType()) {
               case ElementItem:
                  Element e = (Element)item;
                  if (e.getName().equals(XML.USERS_NAME)) {
                     current = new RealmUsersDestination(realm,current);
                     current.send(item);
                  } else if (e.getName().equals(XML.GROUPS_NAME)) {
                     current = new RealmGroupsDestination(realm,current);
                     current.send(item);
                  }
            }
         }
                      
      }
   }
   class RealmUsersDestination implements HandlerDestination {
      HandlerDestination parent;
      Realm realm;
      String alias;
      UUID id;
      String name;
      String email;
      StringBuilder buffer;
      RealmUsersDestination(Realm realm,HandlerDestination parent) {
         this.realm = realm;
         this.parent = parent;
      }
      public int getStartLevel() {
         return 4;
      }
      public HandlerDestination pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (level==5) {
            switch (item.getType()) {
               case ElementItem:
                  Element e = (Element)item;
                  if (e.getName().equals(XML.USER_NAME)) {
                     String sid = e.getAttributeValue("id");
                     alias = e.getAttributeValue("alias");
                     if (sid==null) {
                        log.warning("A realm user is missing the 'id' attribute.");
                        return;
                     }
                     try {
                        id = UUID.fromString(sid);
                     } catch (IllegalArgumentException ex) {
                        log.warning("Bad UUID value '"+sid+"' on realm user.");
                     }
                     name = null;
                     email = null;
                  }
               break;
               case ElementEndItem:
                  if (id!=null) {
                     try {
                        User user = db.getUser(id);
                        if (user==null) {
                           log.warning("Cannot find user with id "+id);
                           return;
                        }
                        RealmUser ruser = db.getRealmUser(realm,id);
                        if (ruser!=null) {
                           log.info("Changing realm user ("+alias+","+id+","+name+","+email+")");
                           if (!ruser.changeAlias(alias)) {
                              log.warning("Could not change alias to '"+alias+"'");
                           }
                           ruser.setName(name);
                           ruser.setEmail(email);
                        } else {
                           log.info("Creating realm user ("+alias+","+id+","+name+","+email+")");
                           ruser = db.createRealmUser(realm,user,alias,name,email);
                           if (ruser==null) {
                              log.warning("Creation of realm user with alias "+alias+" refused.");
                           }
                        }
                        id = null;
                        name = null;
                        email = null;
                        alias = null;
                     } catch (SQLException ex) {
                        throw new XMLException("Database error while creating realm user ("+alias+","+id+")",ex);
                     }
                  }
            }
         } else if (level==6) {
            switch (item.getType()) {
               case ElementItem:
               {
                  Element e = (Element)item;
                  if (e.getName().equals(XML.NAME_NAME)) {
                     buffer = new StringBuilder();
                  } else if (e.getName().equals(XML.EMAIL_NAME)) {
                     buffer = new StringBuilder();
                  }
               }
                  break;
               case ElementEndItem:
               {
                  ElementEnd e = (ElementEnd)item;
                  if (e.getName().equals(XML.NAME_NAME)) {
                     name = buffer.toString();
                  } else if (e.getName().equals(XML.EMAIL_NAME)) {
                     email = buffer.toString();
                  }
                  buffer = null;
                  break;
               }
               case CharactersItem:
                  if (buffer!=null) {
                     buffer.append(((Text)item).getText());
                  }
                  
            }
         } else if (item.getType()==Item.ItemType.CharactersItem &&  buffer!=null) {
            buffer.append(((Text)item).getText());
         }
         
      }
   }
   class RealmGroupsDestination implements HandlerDestination {
      HandlerDestination parent;
      Realm realm;
      Group group;
      RealmGroupsDestination(Realm realm,HandlerDestination parent) {
         this.realm = realm;
         this.parent = parent;
         this.group = null;
      }
      public int getStartLevel() {
         return 4;
      }
      public HandlerDestination pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (level==5) {
            switch (item.getType()) {
               case ElementItem:
                  Element e = (Element)item;
                  if (e.getName().equals(XML.GROUP_NAME)) {
                     String sid = e.getAttributeValue("id");
                     String alias = e.getAttributeValue("alias");
                     if (sid==null) {
                        log.warning("A group is missing the 'id' attribute.");
                        return;
                     }
                     if (alias==null) {
                        log.warning("A group is missing the 'alias' attribute.");
                        return;
                     }
                     try {
                        UUID id = UUID.fromString(sid);
                        group = db.getGroup(realm,id);
                        if (group==null) {
                           log.info("Creating group ("+alias+","+id+")");
                           group = db.createGroup(realm,id,alias);
                        } else {
                           log.info("Group ("+alias+","+id+") already exists.");
                        }
                     } catch (SQLException ex) {
                        throw new XMLException("Database error while creating group ("+alias+","+sid+")",ex);
                     } catch (IllegalArgumentException ex) {
                        log.warning("Bad UUID value '"+sid+"' on group.");
                     }
                  }
               break;
               case ElementEndItem:
                  group = null;
            }
         } else if (level==6) {
            switch (item.getType()) {
               case ElementItem:
               {
                  Element e = (Element)item;
                  if (e.getName().equals(XML.ROLES_NAME)) {
                     current = new GroupRolesDestination(group,current);
                     current.send(item);
                  } else if (e.getName().equals(XML.USERS_NAME)) {
                     current = new GroupUsersDestination(group,current);
                     current.send(item);
                  }
               }
            }
         }
      }
   }
   class GroupRolesDestination implements HandlerDestination {
      HandlerDestination parent;
      Group group;
      GroupRolesDestination(Group group,HandlerDestination parent) {
         this.parent = parent;
         this.group = group;
      }
      public int getStartLevel() {
         return 6;
      }
      public HandlerDestination pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (level==7 && item.getType()==Item.ItemType.ElementItem) {
            Element e = (Element)item;
            if (e.getName().equals(XML.ROLE_NAME)) {
               String sid = e.getAttributeValue("id");
               if (sid==null) {
                  log.warning("A role in group ("+group.getAlias()+","+group.getUUID()+") is missing the 'id' attribute.");
                  return;
               }
               try {
                  UUID id = UUID.fromString(sid);
                  Role role = db.getRole(id);
                  if (role==null) {
                     log.warning("Cannot find role "+id+" for group ("+group.getAlias()+","+group.getUUID()+").");
                  } else {
                     log.info("Adding role ("+role.getName()+","+role.getUUID()+")");
                     group.addRole(role);
                  }
               } catch (SQLException ex) {
                  throw new XMLException("Database error while adding role "+sid+" to group ("+group.getAlias()+","+group.getUUID()+").",ex);
               } catch (IllegalArgumentException ex) {
                  log.warning("Bad UUID value '"+sid+"' on role in group ("+group.getAlias()+","+group.getUUID()+").");
               }
            }
         }
      }
   }
   class GroupUsersDestination implements HandlerDestination {
      HandlerDestination parent;
      Group group;
      GroupUsersDestination(Group group,HandlerDestination parent) {
         this.parent = parent;
         this.group = group;
      }
      public int getStartLevel() {
         return 6;
      }
      public HandlerDestination pop() {
         return parent;
      }
      public void send(Item item)
         throws XMLException
      {
         if (level==7 && item.getType()==Item.ItemType.ElementItem) {
            Element e = (Element)item;
            if (e.getName().equals(XML.USER_NAME)) {
               String sid = e.getAttributeValue("id");
               if (sid==null) {
                  log.warning("A user in group ("+group.getAlias()+","+group.getUUID()+") is missing the 'id' attribute.");
                  return;
               }
               try {
                  UUID id = UUID.fromString(sid);
                  RealmUser user = db.getRealmUser(group.getRealm(),id);
                  if (user==null) {
                     log.warning("Cannot find user "+id+" for group ("+group.getAlias()+","+group.getUUID()+").");
                  } else {
                     log.info("Adding member ("+user.getAlias()+","+user.getUser().getUUID()+")");
                     group.addMember(user);
                  }
               } catch (SQLException ex) {
                  throw new XMLException("Database error while adding user "+sid+" to group ("+group.getAlias()+","+group.getUUID()+").",ex);
               } catch (IllegalArgumentException ex) {
                  log.warning("Bad UUID value '"+sid+"' on user in group ("+group.getAlias()+","+group.getUUID()+").");
               }
            }
         }
      }
   }
   int level;
   HandlerDestination current;
   AuthDB db;
   Logger log;
   /**
    * Creates a new instance of RestoreDestination
    */
   public RestoreDestination(Logger log,AuthDB db)
   {
      this.log = log;
      this.db = db;
      this.level = 0;
      this.current = new HandlerDestination() {
         int flushLevel = -1;
         public int getStartLevel() {
            return 0;
         }
         public HandlerDestination pop() {
            return this;
         }
         public void send(Item item)
            throws XMLException
         {
            if (level==1) {
            } else if (level==2) {
               if (item.getType()==Item.ItemType.ElementItem) {
                  Name name = ((Element)item).getName();
                  if (name.equals(XML.PERMISSIONS_NAME)) {
                     current = new PermissionsDestination(current);
                     current.send(item);
                  } else if (name.equals(XML.ROLES_NAME)) {
                     current = new RolesDestination(current);
                     current.send(item);
                  } else if (name.equals(XML.USERS_NAME)) {
                     current = new UsersDestination(current);
                     current.send(item);
                  } else if (name.equals(XML.REALMS_NAME)) {
                     current = new RealmsDestination(current);
                     current.send(item);
                  }
               }
            }
         }
      };
   }
   
   public void send(Item item)
      throws XMLException
   {
      switch (item.getType()) {
         case ElementItem:
            level++;
            break;
      }
      current.send(item);
      switch (item.getType()) {
         case ElementEndItem:
            level--;
            if (level<current.getStartLevel()) {
               current = current.pop();
            }
      }
   }
   
}
